package connection;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.interfaces.RSAPublicKey;

import javolution.util.FastList;
import javolution.util.FastMap;
import loginserver.clientpackets.AuthGameGuard;
import loginserver.crypt.NewCrypt;
import loginserver.serverpackets.Init;
import util.PacketStream;
import util.Printer;

public class LoginConnection extends Thread
{
	private String hostDestino;
	private int portaDestino;
	private String login, password;
	Socket sock;
	public FastList<String> listServers = new FastList<String>();
	BufferedInputStream in;
	BufferedOutputStream out;
	NewCrypt crypt ;
	NewCrypt staticCrypt ;
	RSAPublicKey _RSApublicKey;
	private int[] _unkGG = new int[4];
	private byte[] _blowfishKey = new byte[16];
	int _protocolVersion;
	private int _sessionId;
	int serverNum = 1;
	boolean terminate = false;
	LoginResult loginResult = null;
	byte loginId[] = new byte[8];
	FastMap<String, Host> serverList = null;
	ConnectionEventReceiver connectionEventReceiver;

	public LoginConnection(ConnectionEventReceiver connectionEventReceiver, String hostDestino, int portaDestino, int serverNum, String login, String password)
	{
		this.connectionEventReceiver = connectionEventReceiver;
		this.hostDestino = hostDestino;
		this.portaDestino = portaDestino;
		this.serverNum = serverNum;
		this.login = login;
		this.password = password;
		this.staticCrypt =	new NewCrypt(true);
	}

	public void fireLogin() throws IOException
	{
		System.out.println("Login Started.");
		sock = new Socket(hostDestino, portaDestino);
		in = new BufferedInputStream(sock.getInputStream());
		out = new BufferedOutputStream(sock.getOutputStream());
		connectionEventReceiver.procConnectionEvent(new Msg(Msg.MSG_TYPE.SUCESS, "LOGIN CONNECTION OK"), ENUM_CONECTION_EVENT.EVT_MSG);
	}


	private byte[] buildLoginPack(String login, String password) throws IOException
	{
		byte byteLogin[] = login.getBytes();
		byte bytePassword[] = password.getBytes();

		ByteBuffer buf = ByteBuffer.allocate(48);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		for (int i = 0; i < buf.capacity(); i++)
			buf.put(i, (byte) 0x00);

		buf.position(1);
		for (int i = 0; i < byteLogin.length; i++)
		{
			buf.put(byteLogin[i]);
		}

		buf.position(15);
		for (int i = 0; i < bytePassword.length; i++)
		{
			buf.put(bytePassword[i]);
		}

		buf.position(31);
		buf.put((byte) 0x08);
		byte ret[] = buf.array();
		NewCrypt.verifyChecksum(ret);
		byte cry[] = crypt.crypt(buf.array());
		return cry;
	}

	private byte[] buildRequestServerListPack(byte loginId1[]) throws IOException
	{
		ByteBuffer buf = ByteBuffer.allocate(24);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		for (int i = 0; i < buf.capacity(); i++)
			buf.put(i, (byte) 0x00);

		buf.position(0);
		buf.put((byte) 0x05);

		for (int i = 0; i < loginId1.length; i++)
			buf.put(loginId1[i]);

		buf.put((byte) 0x04);

		byte ret[] = buf.array();
		NewCrypt.verifyChecksum(ret);
		byte cry[] = crypt.crypt(buf.array());
		return cry;
	}

	private byte[] buildRequestLoginInServer(byte loginId1[], byte serverId) throws IOException
	{
		ByteBuffer buf = ByteBuffer.allocate(24);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		for (int i = 0; i < buf.capacity(); i++)
			buf.put(i, (byte) 0x00);

		buf.position(0);
		buf.put((byte) 0x02);

		for (int i = 0; i < loginId1.length; i++)
			buf.put(loginId1[i]);

		buf.put((byte) serverId);

		byte ret[] = buf.array();
		NewCrypt.verifyChecksum(ret);
		byte cry[] = crypt.crypt(buf.array());
		return cry;
	}

	private void processPlayOkPacket(byte data[]) throws IOException
	{
		ByteBuffer buf = ByteBuffer.wrap(data);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(1);
		LoginResult logRes = new LoginResult();
		logRes.ok = true;
		logRes.login = login;
		logRes.playId1 = buf.getInt();
		logRes.playId2 = buf.getInt();
		logRes.motivo = "PLAY ON SERVER [" + serverNum + "] OK";
		logRes.host = serverList.get(String.valueOf(serverNum));

		buf = ByteBuffer.wrap(loginId);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(0);
		logRes.loginId1 = buf.getInt();
		logRes.loginId2 = buf.getInt();

		loginResult = logRes;
	}

	private void processPlayFailPacket(byte data[]) throws IOException
	{
		ByteBuffer buf = ByteBuffer.wrap(data);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(1);
		LoginResult logRes = new LoginResult();

		byte reason = buf.get();
		switch (reason)
		{
		case 0x0f:
			logRes.motivo = "PLAY FAIL (TOO MANY PLAYERS IN SERVER)";
			break;
		case 0x01:
			logRes.motivo = "PLAY FAIL (SYSTEM ERROR)";
			break;
		case 0x02:
			logRes.motivo = "PLAY FAIL (USER OR PASSWORD WRONG)";
			break;
		default:
			logRes.motivo = "PLAY FAIL (UNKNOW)";
			break;
		}

		loginResult = logRes;
	}

	private void processLoginFailPacket(byte data[]) throws IOException
	{
		ByteBuffer buf = ByteBuffer.wrap(data);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		buf.position(1);
		LoginResult logRes = new LoginResult();

		byte reason = buf.get();

		switch (reason)
		{
		case 0x09:
			logRes.motivo = "LOGIN FAIL (ACCOUNT BANNED)";
			break;
		case 0x07:
			logRes.motivo = "LOGIN FAIL (ACCOUNT IN USE)";
			break;
		case 0x04:
			logRes.motivo = "LOGIN FAIL (ACCESS FAILED)";
			break;
		case 0x03:
			logRes.motivo = "LOGIN FAIL (USER OR PASSWORD IS WRONG)";
			break;
		case 0x02:
			logRes.motivo = "LOGIN FAIL (PASSWORD WRONG)";
			break;
		case 0x01:
			logRes.motivo = "LOGIN FAIL (SYSTEM ERROR)";
			break;
		default:
			logRes.motivo = "LOGIN FAIL (UNKNOW)";
			break;
		}

		loginResult = logRes;
	}

	public void run()
	{
		byte packetData[];
		byte sessionKeyPacket[] = null;

		try
		{
			if (terminate)
				return;
			
			//Process INIT Packet
			sessionKeyPacket = PacketStream.readPacket(in);
			packetData = staticCrypt.decrypt(sessionKeyPacket);
			NewCrypt.decXORPass(packetData);
//			System.out.print(Printer.printData(packetData, packetData.length,"INIT Packet dexored"));
			Init myInit = new Init(packetData);
			myInit.runImpl();
			//Set new BF Key
			_blowfishKey = myInit.get_blowfishKey();
			crypt = new NewCrypt(_blowfishKey);
			
			_protocolVersion = myInit.get_sessionId();
			_RSApublicKey = myInit.get_RSApublicKey();
			_unkGG = myInit.get_unkGG();
			_sessionId = myInit.get_sessionId();
			
			
			//Request GameGuard Auth
			AuthGameGuard myGG = new AuthGameGuard(_sessionId,_unkGG[0],_unkGG[1],_unkGG[2],_unkGG[3]);
			byte[] packGG = new byte[myGG.getMessage().length];
			packGG = myGG.getMessage();
			NewCrypt.appendChecksum(packGG);
			PacketStream.writePacket(out, crypt.crypt(packGG));
			
			byte loginPack[] = buildLoginPack(login, password);
			PacketStream.writePacket(out, loginPack);

			while (!terminate && (packetData = PacketStream.readPacket(in)) != null)
			{
				byte decryptedData[] = crypt.decrypt(packetData);


				if (decryptedData[0] == 0x03)
				{// SHOW_LICENCE
					System.out.print(Printer.printData(decryptedData, decryptedData.length,"SHOW_LICENCE Packet"));// LOG
					System.arraycopy(decryptedData, 1, loginId, 0, loginId.length);
					byte requestServerListPack[] = buildRequestServerListPack(loginId);
					PacketStream.writePacket(out, requestServerListPack);
				} else if (decryptedData[0] == 0x04)
				{// SERVER_LIST
					processServerListPack(decryptedData);
					if (!serverList.containsKey(String.valueOf(serverNum)))
					{
						connectionEventReceiver.procConnectionEvent(new Msg(Msg.MSG_TYPE.ATENTION, "GAME ENTER WORLD"), ENUM_CONECTION_EVENT.EVT_MSG);
						LoginResult logRes = new LoginResult();
						logRes.motivo = "PLAY FAIL (SERVER NUMBER [" + serverNum + "] INVALID)";
						loginResult = logRes;
						setTerminate();
						return;
					}
					byte requestLoginInServer[] = buildRequestLoginInServer(loginId, (byte) serverNum);
					PacketStream.writePacket(out, requestLoginInServer);
				} else if (decryptedData[0] == 0x01)
				{// LOGIN_FAIL
					System.out.print(Printer.printData(decryptedData, decryptedData.length,"LOGIN_FAIL Packet"));// LOG
					processLoginFailPacket(decryptedData);
					return;
				} else if (decryptedData[0] == 0x06)
				{// PLAY_FAIL
					System.out.print(Printer.printData(decryptedData, decryptedData.length,"PLAY_FAIL Packet"));// LOG
					processPlayFailPacket(decryptedData);
					return;
				} else if (decryptedData[0] == 0x07)
				{// PLAY_OK
					System.out.print(Printer.printData(decryptedData, decryptedData.length,"PLAY_OK Packet"));// LOG
					processPlayOkPacket(decryptedData);
					return;
				} else
				{
					System.out.print(Printer.printData(decryptedData, decryptedData.length,"UNKNOW Packet"));// LOG
				}

			}

		}
		catch (IOException e)
		{
			// NADA
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		LoginResult logRes = new LoginResult();
		logRes.motivo = "Connection error";
		loginResult = logRes;
	}

	private void processServerListPack(byte data[])
	{
		int servCount = (data[1] & 0xff);
		System.out.print(Printer.printData(data, data.length,"SERVER_LIST Packet"));// LOG
		serverList = new FastMap<String, Host>();

		int addr[] = new int[4];
		int id = 0;
		int port = 0;
		int off = 0, add = 0;

		off = 3;
		for (int i = 0; i < servCount; i++)
		{
			add = 0;
			port = 0;

			id = (data[off++] & 0xff);
			addr[add++] = data[off++] & 0xff;
			addr[add++] = data[off++] & 0xff;
			addr[add++] = data[off++] & 0xff;
			addr[add++] = data[off++] & 0xff;
			port = data[off++] & 0xff;
			port |= data[off++] << 8 & 0xff00;
			port |= data[off++] << 0x10 & 0xff0000;
			port |= data[off++] << 0x18 & 0xff000000;
			System.out.println(id + "-" + addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3] + ":" + port);
			off += 12;

			Host host = new Host(id, addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3], port);
			serverList.put(String.valueOf(id), host);
		}

	}

	public LoginResult getLoginResult()
	{
		return loginResult;
	}

	public void setTerminate()
	{
		this.terminate = true;
		try
		{
			sock.close();
			in.close();
			out.close();
		}
		catch (IOException e)
		{
			// NADA
		}
	}

}